/*
 * This file contains classes and functions to perform drawing onto the diagram 
 * view.
 */

// ======================== DIAGRAM ELEMENT ========================
// 
// Base class for elements displayed in diagrams. Has a dispatcher, so 
// every model element can be observed by listeners.
function DiagramElement(pos) {
    this.position = (pos !== undefined) ? pos : new Position(0, 0);
    this.parentDiagram = null;
    this.eventDispatcher = new EventDispatcher();
    this.isSelected = false;
    this.uid = defaultUidGenerator.generateUID();
}
DiagramElement.prototype.setUid = function(uid) {
    this.uid = uid;
};
DiagramElement.prototype.notifyPropertyChanged = function(propName) {
    var ev = new Event("propertyChanged", this);
    ev.property = propName;
    this.eventDispatcher.dispatchEvent(ev);
};
DiagramElement.prototype.notifyNeedsRedraw = function() {
    var ev = new Event("needsRedraw", this);
    this.eventDispatcher.dispatchEvent(ev);
};
DiagramElement.prototype.notifyDestroyed = function() {
    var ev = new Event("destroyed", this);
    this.eventDispatcher.dispatchEvent(ev);
};
DiagramElement.prototype.destroy = function() {
    this.parentDiagram = null;
    this.notifyDestroyed();
};
// Get center of this graphic element
DiagramElement.prototype.getCenterPosition = function() {
    return this.position;
};

DiagramElement.prototype.setPosition = function(pos) {
    this.position = pos;
    this.notifyPropertyChanged("position");
    this.notifyNeedsRedraw();
};

DiagramElement.prototype.createEditor = function() {
    throw "DiagramElement.createEditor unimplemented";
};

DiagramElement.prototype.setIsSelected = function(issel) {
    this.isSelected = issel;
    this.notifyPropertyChanged("isSelected");
    this.notifyNeedsRedraw();
};
DiagramElement.prototype.setParentDiagram = function(diag) {
    this.parentDiagram = diag;
    this.notifyPropertyChanged("parentDiagram");
    // no event dispatch needed
};
// Move element relative to its former self
DiagramElement.prototype.moveRelative = function(d) {
    this.position = this.position + d;
    this.notifyPropertyChanged("position");
    this.notifyNeedsRedraw();
};
// Move element to given position
DiagramElement.prototype.moveTo = function(d) {
    this.position = d;
    this.notifyPropertyChanged("position");
    this.notifyNeedsRedraw();
};
// Check point is inside element with given pixel tolerance. 
// (Used to check if item is clicked)
// Returns a callback object thats 'moveTo(pos)' method will be 
// called upon element moving. Returns null if point not part of the element
DiagramElement.prototype.isInnerPoint = function(pos, tolerance) {
    throw "DiagramElement.isInnerPoint unimplemented!";
};
// Event callback
DiagramElement.prototype.handleEvent = function(event) {
    // nop
};
// Draw this element.
DiagramElement.prototype.draw = function() {
    throw "DiagramElement.draw unimplemented!";
};

// ======================== CLASS GRAPHICS ========================
// 
// Class that is the graphical representation of a class in ONE diagram.

ClassGraphics.prototype = new DiagramElement;
function ClassGraphics(pos, model) {
    this.baseClass = DiagramElement;
    this.baseClass(pos);
    // ---
    this.model = model;
    this.dimension = new Dimension(0, 0);
    this.generateRenderer();

    this.model.eventDispatcher.registerListener(this);
}
ClassGraphics.prototype.destroy = function() {
    this.model.eventDispatcher.removeListener(this);
    this.model = null;
    this.constructor.prototype.destroy.call(this);
};

ClassGraphics.prototype.createEditor = function() {
    return new ClassInterfaceEditor(this.model,this.parentDiagram.createEditor());
};

// Get center of this graphic element
ClassGraphics.prototype.getCenterPosition = function() {
    var center = new Position();
    center.x = this.position.x + this.dimension.x * 0.5;
    center.y = this.position.y + this.dimension.y * 0.5;
    return center;
};
// Generate renderer for this class
ClassGraphics.prototype.generateRenderer = function() {
    this.multiblock = generateRendererForClass(this.model);
    this.dimension.x = this.multiblock.requiredWidth;
    this.dimension.y = this.multiblock.requiredHeight;
    this.notifyPropertyChanged("dimension");
};
// Implements the abstract method from DiagramElement.
ClassGraphics.prototype.isInnerPoint = function(pos, tolerance) {
    var t = (tolerance !== undefined) ? tolerance : 0;
    if (pos.x < this.position.x - t)
        return null;
    if (pos.x > this.position.x + this.dimension.x + t)
        return null;
    if (pos.y < this.position.y - t)
        return null;
    if (pos.y > this.position.y + this.dimension.y + t)
        return null;
    return new ClassGraphicsMoveCallback(this);
};
// Implements the abstract method from DiagramElement.
ClassGraphics.prototype.handleEvent = function(event) {

    if (event.source === this.model) {
        if (event.type === "propertyChanged" || event.type === "embeddedPropertyChanged") {
            this.generateRenderer();
            this.notifyNeedsRedraw();
        } else if (event.type === "destroyed") {
            this.parentDiagram.removeElement(this);
        }
    }
};
// Implements the abstract method from DiagramElement.
ClassGraphics.prototype.draw = function() {
//    this.multiblock.drawBlocks(this.parentDiagram.applyInverseViewTransform(this.position), this.isSelected ? "#b0b0ff" : "#ffffff");
    this.multiblock.drawBlocks(this.position, this.isSelected ? "#b0b0ff" : "#ffffff");
};

ClassGraphics.prototype.toString = function() {
    return "[ClassGraphics diagram = " + this.parentDiagram + ", model=" + this.model + "]";
};


// ======================== RELATIONSHIP GRAPHICS ========================
// 
// Class that is the graphical representation of a relationship.

RelationshipGraphics.prototype = new DiagramElement;
function RelationshipGraphics(model, sourceElement, targetElement) {
    this.baseClass = DiagramElement;
    this.baseClass(new Position());
    // ---
    this.model = model;                 // model (relationship) object
    this.sourceElement = sourceElement;    // source's graphic element
    this.targetElement = targetElement;    // target's graphic element

    this.sourceEndPosition = this.sourceElement.position;
    this.targetEndPosition = this.targetElement.position;

    // TODO calculate endpoints
    this.calculateEndpoints();

    this.model.eventDispatcher.registerListener(this);
    this.sourceElement.eventDispatcher.registerListener(this);
    this.targetElement.eventDispatcher.registerListener(this);

    this.position = this.sourceEndPosition;
}

RelationshipGraphics.prototype.destroy = function() {
    this.model.eventDispatcher.removeListener(this);
    this.sourceElement.eventDispatcher.removeListener(this);
    this.targetElement.eventDispatcher.removeListener(this);
    this.model = null;
    this.sourceElement = null;
    this.targetElement = null;
    this.constructor.prototype.destroy.call(this);
};
RelationshipGraphics.prototype.createEditor = function() {
    if (this.model instanceof Association) {
        return new AssociationEditor(this.model,this.parentDiagram.createEditor());
    } else if (this.model instanceof Inheritance) {
        return new InheritanceEditor(this.model,this.parentDiagram.createEditor());
    } else if (this.model instanceof Dependency) {
        return new DependencyEditor(this.model,this.parentDiagram.createEditor());
    } else {
        throw "RelationshipGraphics.createEditor unimplemented for type";
    }
};
// Get center of this graphic element
RelationshipGraphics.prototype.getCenterPosition = function() {
    return this.sourceEndPosition.interpolate(this.targetEndPosition, 0.5);
};

RelationshipGraphics.prototype.setPosition = function(pos) {
    var diff = pos.subtract(this.sourceEndPosition);
    this.sourceEndPosition = this.sourceEndPosition.sum(diff);
    this.targetEndPosition = this.targetEndPosition.sum(diff);
    this.position = this.sourceEndPosition;
    this.calculateEndpoints();
    this.notifyPropertyChanged("position");
    this.notifyNeedsRedraw();
};
RelationshipGraphics.prototype.setSourceEndPosition = function(pos) {
    this.sourceEndPosition = pos;
    this.position = this.sourceEndPosition;
    this.calculateEndpoints();
    this.notifyPropertyChanged("position");
    this.notifyNeedsRedraw();
};
RelationshipGraphics.prototype.setTargetEndPosition = function(pos) {
    this.targetEndPosition = pos;
    this.position = this.sourceEndPosition;
    this.calculateEndpoints();
    this.notifyPropertyChanged("position");
    this.notifyNeedsRedraw();
};
RelationshipGraphics.prototype.calculateEndpoints = function() {
    var res = closestPointsBoxes(this.sourceElement.position, this.sourceElement.dimension,
            this.targetElement.position, this.targetElement.dimension,
            this.sourceEndPosition, this.targetEndPosition);

    this.sourceEndPosition = res.c0;
    this.targetEndPosition = res.c1;
    this.position = this.sourceEndPosition;
};

// Move element relative to its former self
RelationshipGraphics.prototype.moveRelative = function(d) {
    this.sourceEndPosition = this.sourceEndPosition.sum(d);
    this.targetEndPosition = this.targetEndPosition.sum(d);
    this.position = sourceEndPosition;
    this.calculateEndpoints();
    this.notifyPropertyChanged("position");
    this.notifyNeedsRedraw();
};
// Move element to given position
RelationshipGraphics.prototype.moveTo = function(d) {
    var diff = this.targetEndPosition.subtract(this.sourceEndPosition);
    this.sourceEndPosition = d;
    this.targetEndPosition = d.sum(diff);
    this.position = this.sourceEndPosition;
    this.calculateEndpoints();
    this.notifyPropertyChanged("position");
    this.notifyNeedsRedraw();
};
// Implements the abstract method from DiagramElement.
RelationshipGraphics.prototype.isInnerPoint = function(pos, tolerance) {

    var dir = this.targetEndPosition.subtract(this.sourceEndPosition);
    dir.normalize();

    var n = new Position(dir.y, -dir.x); // line normal

    var d_src_p = pos.subtract(this.sourceEndPosition);
    var d_trg_p = pos.subtract(this.targetEndPosition);
    var dot = n.dot(d_src_p);

    if (Math.abs(dot) > tolerance) {
        return null;
    }

    if ((dir.dot(d_src_p) < 0) || (dir.dot(d_trg_p) > 0)) {
        return null;
    }

    return new RelationshipGraphicsMoveCallback(this, 0);
};
// Implements the abstract method from DiagramElement.
RelationshipGraphics.prototype.handleEvent = function(event) {
    // handle events from ends 
    if ((event.source === this.sourceElement) || (event.source === this.targetElement)) {
        if (event.type === "propertyChanged") {
            if (event.property === "position" || event.property === "dimension") {
                this.calculateEndpoints();
                this.notifyNeedsRedraw();
            }
        } else if (event.type === "destroyed") {
            this.parentDiagram.removeElement(this);
        }
    } else if (event.source === this.model) {
        if (event.type === "propertyChanged" || event.type === "embeddedPropertyChanged") {
            this.notifyNeedsRedraw();
        } else if (event.type === "destroyed") {
            this.parentDiagram.removeElement(this);
        }
    }
};
// Implements the abstract method from DiagramElement.
RelationshipGraphics.prototype.draw = function() {
    var color = this.isSelected ? "#0000b0" : "#000000";
    var scale = 12;

    var drawStart = this.sourceEndPosition;
    var drawEnd = this.targetEndPosition;

    if (this.model instanceof Inheritance) {
        if (this.model.isGeneralization) {
            drawGeneralization(drawStart, drawEnd, color, scale);
        } else if (this.model.isRealization) {
            drawRealization(drawStart, drawEnd, color, scale);
        } else {
            // FIXME error
        }
    } else if (this.model instanceof Association) {
        if (this.model.isAssociation) {
            drawAssociation(drawStart, drawEnd, this.model.sourceEndInfo.isNavigable, this.model.targetEndInfo.isNavigable, color, scale);
        } else if (this.model.isAggregation) {
            drawAggregation(drawStart, drawEnd, this.model.sourceEndInfo.isNavigable, this.model.targetEndInfo.isNavigable, color, scale);
        } else if (this.model.isComposition) {
            drawComposition(drawStart, drawEnd, this.model.sourceEndInfo.isNavigable, this.model.targetEndInfo.isNavigable, color, scale);
        } else {
            // FIXME error
        }
    } else if (this.model instanceof Dependency) {
        drawDependency(drawStart, drawEnd, color, scale);
    } else {
        // FIXME error
    }

    if (this.model.stereotypes.length > 0) {
        var tf = new TextFormat();
        tf.fontColor = color;
        drawRelationshipStereotype(drawStart, drawEnd, generateStringFromStereotypes(this.model.stereotypes), tf);
    }
};

RelationshipGraphics.prototype.toString = function() {
    return "[RelationshipGraphics diagram = " + this.parentDiagram + ", model=" + this.model + ", srcEnd=" + this.sourceElement + " targetEnd=" + this.targetElement + "]";
};


// ======================== DIAGRAM ========================
// 
// Class that representation the diagram.
function Diagram(name) {

    this.uid = defaultUidGenerator.generateUID();
    // name of the diagram
    this.name = name;
    // dimensions of the view (visible diagram area)
    this.viewDim = new Dimension(canvas.width, canvas.height);
    // dimension of the whole diagram (tipically greater than the view)
    this.diagramDim = new Dimension(1.3 * canvas.width, 1.3 * canvas.height);
    // current view's upper-left corner position
    this.viewPosition = new Position(0, 0);
    // diagram elements displayed on the diagram
    this.elements = new Array();
    // currently selected element
    this.selectedElement = null;
    // event source
    this.eventDispatcher = new EventDispatcher();
    // tolerance of misclicking
    this.clickThreshold = 10;
    // tolerance of mouse movement before considering it moving
    this.moveThreshold = 5;

    // flag: diagram is the currently shown one
    this.isCurrent = false;
    // flag: diagram is in the open diagrams list
    this.isActive = false;

    // callback for element moving
    this.selectedElementMoveCallback = null;
    // callback for diagram view moving
    this.diagramViewMoveCallback = null;

    // drag callback for left mouse button dragging
    this.leftMouseDragCallback = null;
    // drag callback for left mouse button clicking
    this.leftMouseClickCallback = null;

    this.maxDrawingFps = 60;
    this.isRedrawRequested = false;
    this.intervalVariable = null;
    this.setRedrawTimer();
}

Diagram.prototype.notifPropertyChanged = function(propName) {
    var ev = new Event("propertyChanged", this);
    ev.property = propName;
    this.eventDispatcher.dispatchEvent(ev);
};
Diagram.prototype.notifyElementListChanged = function(newElement) {
    var ev = new Event("elementListChanged", this);
    ev.newElement = newElement;
    this.eventDispatcher.dispatchEvent(ev);
};
Diagram.prototype.notifySelectionChanged = function(selectedElement) {
    var ev = new Event("selectionChanged", this);
    ev.selectedElement = selectedElement;
    this.eventDispatcher.dispatchEvent(ev);
};
Diagram.prototype.notifyViewChanged = function() {
    var ev = new Event("viewChanged", this);
    ev.viewPosition = this.viewPosition;
    ev.viewDimension = this.viewDim;
    ev.diagramDimension = this.diagramDim;
    this.eventDispatcher.dispatchEvent(ev);
};

Diagram.prototype.destroy = function() {
    for(var i=0; i<this.elements.length; i++){
        this.elements[i].destroy();
    }
    this.clearRedrawTimer();
};

Diagram.prototype.createGraphicsForModelElement = function(modelElement) {
    // regster for events from this element (to commense redrawing)
    if (modelElement === undefined || modelElement === null) {
        return {error: "YouShouldNotSeeThis", result: null};
    }

    var graphics = null;

    if (modelElement instanceof Class) {
        graphics = new ClassGraphics(this.applyViewTransform(new Position(20, 20)), modelElement);
        return {error: null, result: graphics};

    } else if (modelElement instanceof Relationship) {
        var srcGraph = this.getElementByModelId(modelElement.sourceEnd.uid);
        var trgGraph = this.getElementByModelId(modelElement.targetEnd.uid);

        if (srcGraph === null || trgGraph === null) {
            // some of endpoints are not on the diagram
            return {error: "Both ends of the relationship must be on the diagram (" +
                        modelElement.sourceEnd.name + " and " + modelElement.targetEnd.name + ")", result: null};
        }

        graphics = new RelationshipGraphics(modelElement, srcGraph, trgGraph);
        return {error: null, result: graphics};
    } else {
        // not representable element
        return {error: "YouShouldNotSeeThis", result: null};
    }
};
Diagram.prototype.addElement = function(element) {
    // register for events from this element (to commense redrawing)
    element.setParentDiagram(this);
    element.eventDispatcher.registerListener(this);
    // store element
    this.elements.push(element);
    // notify listeners of the newcomer
    this.notifyElementListChanged(element);
    if (this.isCurrent) {
        this.requestRedraw();
    }
};
Diagram.prototype.removeElement = function(element) {
    // unregster for events from this element (to commense redrawing)
    element.eventDispatcher.removeListener(this);
    // remove element
    element.destroy();
    this.elements.remove(element);
    // notify listeners of the deleted element
    this.notifyElementListChanged(element);

    if (this.isCurrent) {
        this.requestRedraw();
    }
};
// Get element at position, filter by class of element (must be 
// instanceof classFilter)
Diagram.prototype.getElementByPosition = function(pos, classFilter) {
    for (var i = 0; i < this.elements.length; i++) {
        if (this.elements[i].isInnerPoint(pos, this.clickThreshold)) {
            if (classFilter !== undefined) {
                if (this.elements[i] instanceof classFilter) {
                    return this.elements[i];
                }
            } else {
                return this.elements[i];
            }
        }
    }
    return null;
};
// Get element with model of id 'modelId'
Diagram.prototype.getElementByModelId = function(modelId) {
    for (var i = 0; i < this.elements.length; i++) {
        // Note: here == is used in comparison to allow for string-integer 
        // comparision and yield equality when the string value is the same 
        // as the integer (modelId is a string)
        if (this.elements[i].model !== undefined && this.elements[i].model !== null && this.elements[i].model.uid == modelId) {
            return this.elements[i];
        }
    }
    return null;
};
// Get diagram element of id 'diagramId'
Diagram.prototype.getElementByDiagramId = function(diagramId) {
    var numId = Number(diagramId);
    for (var i = 0; i < this.elements.length; i++) {
        if (this.elements[i].uid === numId)
        {
            return this.elements[i];
        }
    }
    return null;
};
Diagram.prototype.setName = function(name) {
    this.name = name;
    this.notifPropertyChanged("name");
};
Diagram.prototype.setLeftMouseDragCallback = function(callback) {
    this.leftMouseDragCallback = callback;
};
Diagram.prototype.setLeftMouseClickCallback = function(callback) {
    this.leftMouseClickCallback = callback;
};
Diagram.prototype.setAsCurrent = function(isCurrent) {
    this.isCurrent = isCurrent;
};
Diagram.prototype.setAsActive = function(isActive) {
    this.isActive = isActive;
};
Diagram.prototype.setSelectedElement = function(sel) {
    // unselect current one
    var selectionChanged = (this.selectedElement !== sel);

    if (this.selectedElement !== null) {
        this.selectedElement.setIsSelected(false);
    }
    // select the new one
    if (sel !== null) {
        sel.setIsSelected(true);
    }
    this.selectedElement = sel;
    // notify listeners
    if (selectionChanged)
        this.notifySelectionChanged(sel);
};
Diagram.prototype.setViewPosition = function(viewPos) {

    this.viewPosition = viewPos;
    // notify listeners
    this.notifyViewChanged();

    if (this.isCurrent) {
        this.setCanvasTransform();
        this.requestRedraw();
    }
};
Diagram.prototype.setViewCenter = function(centerPos) {

    var newViewPos = new Position();
    newViewPos.x = Clamp(centerPos.x - this.viewDim.x * 0.5, 0, this.diagramDim.x - this.viewDim.x);
    newViewPos.y = Clamp(centerPos.y - this.viewDim.y * 0.5, 0, this.diagramDim.y - this.viewDim.y);

    this.setViewPosition(newViewPos);
};
Diagram.prototype.setViewDimension = function(viewDim) {
    // unselect current one
    this.viewDim = viewDim;

    this.diagramDim.x = Math.max(2 * this.viewDim.x, this.diagramDim.x);
    this.diagramDim.y = Math.max(2 * this.viewDim.y, this.diagramDim.y);
    // notify listeners
    this.notifyViewChanged();

    if (this.isCurrent) {
        this.setCanvasTransform();
        this.requestRedraw();
    }
};
Diagram.prototype.setCanvasTransform = function() {
    canvasCtx.setTransform(1, 0, 0, 1, -this.viewPosition.x, -this.viewPosition.y);
};
Diagram.prototype.clear = function() {
    canvasCtx.setTransform(1, 0, 0, 1, 0, 0);
    canvasCtx.clearRect(0, 0, canvas.width, canvas.height);
    this.setCanvasTransform();
};
Diagram.prototype.setRedrawTimer = function() {
    var me = this;
    this.intervalVariable = window.setInterval(function() {
        me.handleInterval();
    }, 1000 / this.maxDrawingFps);
};
Diagram.prototype.clearRedrawTimer = function() {
    if(this.intervalVariable) {
        window.clearInterval(this.intervalVariable);
        this.intervalVariable = null;
    }
};
Diagram.prototype.requestRedraw = function() {
    this.isRedrawRequested = true;
};
Diagram.prototype.draw = function() {
    if (!this.isCurrent) {
        throw "Diagram.draw called when it is not current";
    }

    this.clear();
    for (var i = 0; i < this.elements.length; i++) {
        this.elements[i].draw();
    }

    drawBox1px(5, 5, this.diagramDim.x - 10, this.diagramDim.y - 10, "#c0c0c0");
};

Diagram.prototype.applyViewTransform = function(canvasPos) {
    return this.viewPosition.sum(canvasPos);
};

Diagram.prototype.moveViewTo = function(diagramPos) {
    this.setViewPosition(diagramPos);
};

// Event callback (dispatchers)
Diagram.prototype.handleEvent = function(event) {
    if (event.type === "needsRedraw") {
        this.requestRedraw();
    }

};
// Event callback (dispatchers)
Diagram.prototype.handleInterval = function() {
    if (this.isCurrent && this.isRedrawRequested) {
        this.isRedrawRequested = false;
        this.draw();
    }
};
// Event callback (dispatchers)
Diagram.prototype.createEditor = function() {
    return new DiagramEditor(this);
};
// Click event callback
Diagram.prototype.evDiagramClick = function(event) {
    event = event || window.event;

    var clickPos = canvas.getMouseEventCoords(event);
    var posx = clickPos.x;
    var posy = clickPos.y;
    var pos = new Position(posx, posy);

    if (this.leftMouseClickCallback !== null) {
        this.leftMouseClickCallback.evMouseClick(this.applyViewTransform(pos));
        this.leftMouseClickCallback = null;
    }

    return false;
};
// Mouse down event callback
Diagram.prototype.evDiagramMouseDown = function(event) {
    event = event || window.event;

    var clickPos = canvas.getMouseEventCoords(event);
    var posx = clickPos.x;
    var posy = clickPos.y;
    var pos = new Position(posx, posy);

    if (event.button === 0) {

        if (this.leftMouseDragCallback !== null) {

            this.leftMouseDragCallback.evMouseDown(this.applyViewTransform(pos));

        } else {
            this.setSelectedElement(null);
            this.selectedElementMoveCallback = null;

            //        var e = this.getElementByPosition(this.applyViewTransform(pos));

            for (var i = 0; i < this.elements.length; i++) {
                var cb = this.elements[i].isInnerPoint(this.applyViewTransform(pos), this.clickThreshold);
                if (cb !== null) {
                    this.setSelectedElement(this.elements[i]);
                    this.selectedElementMoveCallback = cb;
                    this.selectedElementMoveCallback.moveThreshold = this.moveThreshold;
                    // Note: pos is intentionally not transformed, because the 
                    // callback constantly changed the view position.
                    this.selectedElementMoveCallback.evMouseDown(this.applyViewTransform(pos));
                    break;
                }
            }
        }

    } else if (event.button === 2) {

        this.diagramViewMoveCallback = new DiagramViewDragCallback(this);
        this.diagramViewMoveCallback.evMouseDown(pos);
    }

    return false;
};
// Mouse up event callback
Diagram.prototype.evDiagramMouseUp = function(event) {
    event = event || window.event;

    var clickPos = canvas.getMouseEventCoords(event);
    var posx = clickPos.x;
    var posy = clickPos.y;
    var pos = new Position(posx, posy);

    if (event.button === 0) {

        if (this.leftMouseDragCallback !== null) {

            this.leftMouseDragCallback.evMouseUp(this.applyViewTransform(pos));
            this.leftMouseDragCallback = null;

        } else {
            if (this.selectedElementMoveCallback !== null) {
                this.selectedElementMoveCallback.evMouseUp(this.applyViewTransform(pos));
                this.selectedElementMoveCallback = null;
            }
        }
    } else if (event.button === 2) {
        if (this.diagramViewMoveCallback !== null) {
            // Note: pos is intentionally not transformed, because the 
            // callback constantly changed the view position.
            this.diagramViewMoveCallback.evMouseUp(pos);
            this.diagramViewMoveCallback = null;
        }
    }

    return false;
};
// Mouse move event callback
Diagram.prototype.evDiagramMouseMove = function(event) {
    event = event || window.event;

    var clickPos = canvas.getMouseEventCoords(event);
    var posx = clickPos.x;
    var posy = clickPos.y;
    var pos = new Position(posx, posy);

    if (this.leftMouseDragCallback !== null) {

        this.leftMouseDragCallback.evMouseMove(this.applyViewTransform(pos));

    } else {
        if (this.selectedElementMoveCallback !== null) {
            this.selectedElementMoveCallback.evMouseMove(this.applyViewTransform(pos));
        }
    }

    if (this.diagramViewMoveCallback !== null) {
        // Note: pos is intentionally not transformed, because the 
        // callback constantly changed the view position.
        this.diagramViewMoveCallback.evMouseMove(pos);
    }

    return false;
};
// Mouse out event callback
Diagram.prototype.evDiagramMouseOut = function(event) {
    event = event || window.event;

    // disable dragging, and reset mouse button states

    if (this.leftMouseClickCallback !== null) {
        this.leftMouseClickCallback = null;
    }

    if (this.leftMouseDragCallback !== null) {
        this.leftMouseDragCallback.forceEndDragging();
        this.leftMouseDragCallback = null;
    }

    if (this.selectedElementMoveCallback !== null) {
        this.selectedElementMoveCallback.forceEndDragging();
        this.selectedElementMoveCallback = null;
    }
    if (this.diagramViewMoveCallback !== null) {
        this.diagramViewMoveCallback.forceEndDragging();
        this.diagramViewMoveCallback = null;
    }

    return false;
};
// Switched to different diagram event callback
Diagram.prototype.evDiagramLeave = function() {

    this.setAsCurrent(false);

    // disable dragging, and reset mouse button states

    if (this.leftMouseClickCallback !== null) {
        this.leftMouseClickCallback = null;
    }

    if (this.leftMouseDragCallback !== null) {
        this.leftMouseDragCallback.forceEndDragging();
        this.leftMouseDragCallback = null;
    }

    if (this.selectedElementMoveCallback !== null) {
        this.selectedElementMoveCallback.forceEndDragging();
        this.selectedElementMoveCallback = null;
    }
    if (this.diagramViewMoveCallback !== null) {
        this.diagramViewMoveCallback.forceEndDragging();
        this.diagramViewMoveCallback = null;
    }

    this.clear();

    return false;
};
// Switched to this diagram event callback
Diagram.prototype.evDiagramEnter = function() {

    this.setAsCurrent(true);
    this.setCanvasTransform();
    this.draw();
    return false;
};
// Switched to this diagram event callback
Diagram.prototype.evCanvasDimensionChanged = function(canvasDim) {

    this.setViewDimension(canvasDim);
    return false;
};
// Switched to this diagram event callback
Diagram.prototype.toString = function() {
    return "[Diagram name=" + this.name + "]";
};


// ========================  ========================

// Set generator back to its initial state
defaultUidGenerator.setNextValue(100000);

// ======================== XML WRITING FUNCTIONS ========================
// 
//
Diagram.prototype.toXml = function(xmlWriter) {
    xmlWriter.beginNode("diagram");
    xmlWriter.addAttribute("name", this.name);
    xmlWriter.addAttribute("dimension", this.diagramDim.x + "," + this.diagramDim.y);
    xmlWriter.addAttribute("viewPosition", this.viewPosition.x + "," + this.viewPosition.y);
    xmlWriter.beginNodeChildren();

    xmlWriter.addCommentChild("Diagram elements go here");

    for (var i = 0; i < this.elements.length; i++) {
        this.elements[i].toXml(xmlWriter);
    }

    xmlWriter.endNodeChildren();
    xmlWriter.endNode();
};
DiagramElement.prototype.toXml = function(xmlWriter) {
//    throw "DiagramElement.toXmlString unimplemented"
};
ClassGraphics.prototype.toXml = function(xmlWriter) {
    xmlWriter.beginNode("class-element");
    xmlWriter.addAttribute("id", this.uid);
    xmlWriter.addAttribute("modelRef", resolveTypeRef(this.model.uid));
    xmlWriter.addAttribute("position", this.position.x + "," + this.position.y);
    xmlWriter.endNode();
};
RelationshipGraphics.prototype.toXml = function(xmlWriter) {
    xmlWriter.beginNode("relationship-element");
    xmlWriter.addAttribute("id", this.uid);
    xmlWriter.addAttribute("sourceElement", resolveTypeRef(this.sourceElement.uid));
    xmlWriter.addAttribute("targetElement", resolveTypeRef(this.targetElement.uid));
    xmlWriter.addAttribute("sourcePosition", this.sourceEndPosition.x + "," + this.sourceEndPosition.y);
    xmlWriter.addAttribute("targetPosition", this.targetEndPosition.x + "," + this.targetEndPosition.y);
    xmlWriter.addAttribute("modelRef", resolveTypeRef(this.model.uid));
    xmlWriter.endNode();
};
Diagram.prototype.toXml = function(xmlWriter) {
    xmlWriter.beginNode("diagram");
    xmlWriter.addAttribute("name", this.name);
    xmlWriter.addAttribute("dimension", this.diagramDim.x + "," + this.diagramDim.y);
    xmlWriter.addAttribute("viewPosition", this.viewPosition.x + "," + this.viewPosition.y);
    xmlWriter.beginNodeChildren();

    xmlWriter.addCommentChild("Diagram elements go here");

    for (var i = 0; i < this.elements.length; i++) {
        this.elements[i].toXml(xmlWriter);
    }

    xmlWriter.endNodeChildren();
    xmlWriter.endNode();
};